﻿/** uupaa-excanvas.js
*
* uupaa-excanvas.js is HTML5::Canvas support javascript library
*  - uupaa.js spin-off project
*
* @author Takao Obara <uupaa.js@gmail.com>
* @license Dual licensed under the MIT and Apache(Apache License, Version 2.0) licenses.
*          The VML part is based on the idea of ExplorerCanvas.
* @version 0.1
* @date 2008-12-01
* @see <a href="http://www.whatwg.org/specs/web-apps/current-work/#the-canvas-element">HTML5 spec</a>
* @see <a href="http://excanvas.sourceforge.net/">ExplorerCanvas</a>
* @see <a href="http://code.google.com/p/uupaa-js/">uupaa.js Home(Google Code)</a>
* @see <a href="http://code.google.com/p/uupaa-js-spinoff/">uupaa.js SpinOff Project Home(Google Code)</a>
* @see <a href="http://msdn.microsoft.com/en-us/library/bb979679(VS.95).aspx">Silverlight Reference(MSDN)</a>
*/
define(function (require, exports, module) {
    if (!uuClass.Canvas) {
        /** Canvas
        *
        * @class
        */
        uuClass.Canvas = function (canvas, vml) {
			var agent = navigator.userAgent.toLowerCase();
            return (!document.uniqueID || /trident/.test(agent)) ? canvas : uuClass.Canvas._initDynamicElement(canvas, vml);
        };

        // uuClass.Canvas.xamlsrc - XAML file path
        // "#xaml" is inline XAML id
        uuClass.Canvas.xamlsrc = "#xaml";

        /** Matrix
        *
        * @class
        */
        uuClass.Matrix = {
            identity: function () {
                return [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
            },
            multiply: function (m1, m2) {
                var rv = uuClass.Matrix.identity(), x, y, z, sum;
                for (x = 0; x < 3; ++x) {
                    for (y = 0; y < 3; ++y) {
                        for (sum = 0, z = 0; z < 3; ++z) {
                            sum += m1[x][z] * m2[z][y];
                        }
                        rv[x][y] = sum;
                    }
                }
                return rv;
            },
            translate: function (x, y) {
                return [[1, 0, 0],
                          [0, 1, 0],
                          [x, y, 1]];
            },
            rotate: function (angle) {
                var c = Math.cos(angle), s = Math.sin(angle);
                return [[c, s, 0],
                          [-s, c, 0],
                          [0, 0, 1]];
            },
            scale: function (x, y) {
                return [[x, 0, 0],
                          [0, y, 0],
                          [0, 0, 1]];
            },
            transform: function (m11, m12, m21, m22, dx, dy) {
                return [[m11, m12, 0],
                          [m21, m22, 0],
                          [dx, dy, 1]];
            }
        };
        // to degree. from java.math
        Math.toDegrees || (Math.toDegrees = 180 / Math.PI);
        // to radian. from java.math
        Math.toRadians || (Math.toRadians = Math.PI / 180);

        // --- local scope ------------------------------------------------------
        (function () {
            var _win = window, _doc = document,
        _matrix = uuClass.Matrix,
        _int = parseInt, _round = Math.round,
        _sin = Math.sin, _cos = Math.cos,
        _zoom = 10, _halfZoom = _zoom / 2,
        _colorDict = new uuClass.ColorDictionary(),
        _colorAlpha = uuClass.Color._hexAlpha,
        _pixel = { em: 0, pt: 0 },
        _textRectElement = null,
            // canvas ready flag
        _ready = 0,
            // xaml file default path
        _env = { xamlsrc: uuClass.Canvas.xamlsrc },

            // --- const ---
        HitProps = { width: 1, height: 2 },
            //  GradientType  = { linear: 1, radial: 2, tile: 3 },
        Composite = { "source-over": 1, "destination-over": 5, copy: 11 },
        TextAlign = { start: 1, left: 1, center: 2, right: 3, end: 3 },
        SaveProps = { strokeStyle: 1, fillStyle: 1, globalAlpha: 1,
            lineWidth: 1, lineCap: 1, lineJoin: 1, miterLimit: 1,
            shadowOffsetX: 1, shadowOffsetY: 1, shadowBlur: 1, shadowColor: 1,
            globalCompositeOperation: 1, font: 1, textAlign: 1, textBaseline: 1,
            _lineScale: 1, _scaleX: 1, _scaleY: 1, _mtxEffected: 1, _clipPath: 1
        },
        AgCaps = { square: "Square", butt: "Flat", round: "Round" },
        VMLCaps = { square: "square", butt: "flat", round: "round" },
        AgLineJoin = { round: "Round", bevel: "Bevel", miter: "Miter" },
        AgFontStyle = { normal: "Normal", italic: "Italic", oblique: "Italic" },
        AgFontWeight = { normal: "Normal", bold: "Bold", bolder: "ExtraBold",
            lighter: "Thin", "100": "Thin", "200": "ExtraLight",
            "300": "Light", "400": "Normal", "500": "Medium",
            "600": "SemiBold", "700": "Bold", "800": "ExtraBold",
            "900": "Black"
        };

            // --- uuClass.Canvas static functions ---
            uuClass.Canvas.ready = function (fn, canvas) {
                var tm, ary, i, iz;
                if (canvas) {
                    if (!_doc.uniqueID) { fn(); return; }
                    ary = (canvas instanceof Array) ? canvas : [canvas];
                    i = 0, iz = ary.length;
                    tm = _win.setInterval(function () {
                        for (i = 0; i < iz; ++i) {
                            if (!ary[i].getContext().contextReady) { return; }
                        }
                        clearInterval(tm);
                        if (fn != null)
                            fn();
                    }, 16);
                } else {
                    tm = _win.setInterval(function () {
                        if (_ready) {
                            clearInterval(tm);
                            if (fn != null)
                                fn();
                        }
                    }, 16);
                }
            };

            uuClass.Canvas.already = function (canvas) {
                if (canvas) {
                    if (!_doc.uniqueID) { return true; }
                    var ary = (canvas instanceof Array) ? canvas : [canvas],
        i = 0, iz = ary.length;
                    for (; i < iz; ++i) {
                        if (!ary[i].getContext().contextReady) { return false; }
                    }
                    return true;
                }
                return !!_ready;
            };

            uuClass.Canvas._initDynamicElement = function (canvas, vml) {
                if (vml) {
                    return !canvas.getContext ? VMLInitDynamicElement(canvas) : canvas;
                }
                return !canvas.getContext ? null : canvas;
            };

            // --- measure unit(pt, em) ---
            function unit(cache) {
                var rv = _pixel, e;
                if ((cache !== void 0 && !cache) || !rv.em) {
                    e = _doc.body.appendChild(_doc.createElement("div"));
                    e.style.width = "12em"; rv.em = e.clientWidth / 12;
                    e.style.height = "12pt"; rv.pt = e.clientHeight / 12;
                    _doc.body.removeChild(e);
                }
                return rv;
            }

            // --- measure text rect(width, height) ---
            function textRect(text, font) {
                if (!_textRectElement) {
                    _textRectElement = _doc.createElement("div");
                    _textRectElement.id = "uupaa-canvas-text-rect";
                    // for debug
                    //  _textRectElement.style.cssText = "position:absolute;text-align:left;border:1px solid red";
                    _textRectElement.style.cssText = "position:absolute;top:-9999px;left:-9999px;text-align:left;visibility:hidden;border:1px solid red";
                    _doc.body.appendChild(_textRectElement);
                }
                _textRectElement.style.font = font;
                _textRectElement.innerText = text;
                var box = _textRectElement.getBoundingClientRect(), w, h;
                w = box.right - box.left;
                h = box.bottom - box.top;
                return { w: _round(w), h: _round(h) };
            }

            // --- parse "font:" ---
            function parseFontCSS(font) {
                var rv = { FontStyle: "normal", FontVariant: "normal",
                    FontWeight: "normal", FontSize: "medium",
                    LineHeight: "1", FontFamily: ""
                }, m;

                if ((m = font.match(/^(normal|italic|oblique)/))) {
                    rv.FontStyle = m[1];
                    font = font.slice(m[0].length);
                }
                if ((m = font.match(/^\s*(normal|small-caps)/))) {
                    rv.FontVariant = m[1];
                    font = font.slice(m[0].length);
                }
                if ((m = font.match(/^\s*(normal|bolder|bold|lighter|100|200|300|400|500|600|700|800|900)/))) {
                    rv.FontWeight = m[1];
                    font = font.slice(m[0].length);
                }
                if ((m = font.match(/^\s*(xx-small|x-small|small|medium|large|x-large|xx-large|larger|smaller|[\d]+(px|em|pt))/))) {
                    if (m[2]) {
                        switch (m[2]) {
                            case "px": rv.FontSize = _int(m[1]); break;
                            case "em": rv.FontSize = _int(m[1]) * unit().em; break;
                            case "pt": rv.FontSize = _int(m[1]) * unit().pt; break;
                        }
                    } else {
                        rv.FontSize = m[1];
                    }
                    font = font.slice(m[0].length);
                }
                if ((m = font.match(/^\s*\/\s*(normal|[\d\.]+(px|em|pt)|[\d\.]+)/))) {
                    if (m[2]) {
                        switch (m[2]) {
                            case "px": rv.LineHeight = _int(m[1]); break;
                            case "em": rv.LineHeight = _int(m[1]) * unit().em; break;
                            case "pt": rv.LineHeight = _int(m[1]) * unit().pt; break;
                        }
                    } else {
                        if (m[1] === "normal") {
                            m[1] = 1;
                        }
                        rv.LineHeight = m[1];
                    }
                    font = font.slice(m[0].length);
                }
                rv.FontFamily = font.replace(/^\s*/, "").replace(/,/g, ",,").replace(/\"\'/g, "");
                return rv;
            }

            function fontScalFineTuning(fontSize, vml) {
                var scale = 1.0, margin = 0;
                if (vml) {
                    // VML
                    if (fontSize <= 10) { scale = 0.80; margin = 0; }
                    else if (fontSize <= 20) { scale = 0.92; margin = 0; }
                    else if (fontSize <= 30) { scale = 0.97; margin = 0; }
                    else if (fontSize <= 40) { scale = 0.98; margin = 0; }
                    else if (fontSize <= 50) { scale = 0.98; margin = -2; }
                    else if (fontSize <= 60) { scale = 0.99; margin = -4; }
                    else { scale = 1.0; margin = 0; }
                } else {
                    // Silverlight
                    if (fontSize <= 10) { scale = 0.88; margin = 0; }
                    else if (fontSize <= 20) { scale = 0.90; margin = -3; }
                    else if (fontSize <= 30) { scale = 0.93; margin = -5; }
                    else if (fontSize <= 40) { scale = 0.97; margin = -6; }
                    else if (fontSize <= 50) { scale = 0.98; margin = -10; }
                    else if (fontSize <= 60) { scale = 0.98; margin = -12; }
                    else if (fontSize <= 80) { scale = 0.98; margin = -12; }
                    else if (fontSize <= 100) { scale = 0.98; margin = -16; }
                    else if (fontSize <= 120) { scale = 0.99; margin = -16; }
                    else if (fontSize <= 140) { scale = 0.99; margin = -26; }
                    else { scale = 1.0; margin = -16; }
                }
                return { scale: scale, margin: margin };
            }

            // --- detect clip and clear color ---
            function detectBackgroundColor(node) {
                var bg = "white", c, n = node;
                while (n) {
                    if (n.currentStyle) {
                        bg = n.currentStyle.backgroundColor;
                        if (bg !== "transparent") {
                            break;
                        }
                    }
                    n = n.parentNode;
                }
                c = _colorAlpha(bg === "transparent" ? "white" : bg);
                return c[0];
            }

            // --- event handler ---
            function onPropertyChange(evt) {
                var tgt, prop;
                if ((prop = HitProps[evt.propertyName] || 0)) {
                    tgt = evt.srcElement;
                    if (prop === 1) {
                        // 1: width
                        tgt.style.width = tgt.attributes.width.nodeValue + "px";
                        uuClass.Canvas.ready() && tgt.getContext()._clear();
                    } else {
                        // 2: height
                        tgt.style.height = tgt.attributes.height.nodeValue + "px";
                        uuClass.Canvas.ready() && tgt.getContext()._clear();
                    }
                }
            }

            function onResize(evt) {
                var tgt = evt.srcElement;
                if (tgt.firstChild) {
                    tgt.firstChild.style.cssText = "width:" + tgt.clientWidth + "px" +
                                   "height:" + tgt.clientHeight + "px";
                }
            }

            // --- VML ---
            function VMLInitDynamicElement(elm) {
                var e = _doc.createElement(elm.outerHTML), attrs;
                if (elm.parentNode) {
                    elm.parentNode.replaceChild(e, elm);
                } else {
                    e = elm;
                }

                e.getContext = function () {
                    return e._ctx2d ? e._ctx2d : (e._ctx2d = new VML2D(e));
                };
                e.attachEvent("onpropertychange", onPropertyChange);
                e.attachEvent("onresize", onResize);

                attrs = e.attributes;
                if (attrs.width && attrs.width.specified) {
                    e.style.width = attrs.width.nodeValue + "px";
                } else {
                    e.width = e.clientWidth;
                }
                if (attrs.height && attrs.height.specified) {
                    e.style.height = attrs.height.nodeValue + "px";
                } else {
                    e.height = e.clientHeight;
                }
                return e;
            }

            function VML2D(canvasElement) {
                // canvas 2d context properties
                this.canvas = canvasElement;

                // compositing
                this.globalAlpha = 1.0;
                this.globalCompositeOperation = "source-over";

                // colors and styles
                this.strokeStyle = "#000000";
                this.fillStyle = "#000000";

                // line caps/joins
                this.lineWidth = 1;
                this.lineCap = "butt";
                this.lineJoin = "miter";
                this.miterLimit = 10;

                // shadows
                this.shadowOffsetX = 0;
                this.shadowOffsetY = 0;
                this.shadowBlur = 0;
                this.shadowColor = "rgba(0,0,0,0)";

                // text
                this.font = "10px sans-serif";
                this.textAlign = "start";
                this.textBaseline = "alphabetic";

                // enlarge properties
                this.xClearColor = "#ffffff";
                this.xClipStyle = "#ffffff";
                this.xFontScaleW = 1.0;
                this.xFontScaleH = 0.9;
                this.xTextMarginTop = 0;
                this.xType = "VML2D";


                // hidden properties
                // 1: already
                this.contextReady = 1;
                this._lineScale = 1;
                this._scaleX = 1;
                this._scaleY = 1;
                // 1: matrix effected
                this._mtxEffected = 0;
                this._zindex = -1;

                // inner param
                this._mtx = _matrix.identity();
                // matrix and prop stack.
                this._stack = [];
                // current path
                this._path = [];
                // clipping path
                this._clipPath = null;
                // current position x
                this._px = 0;
                // current position y
                this._py = 0;

                this._elm = canvasElement.appendChild(_doc.createElement("div"));
                this._elm.style.width = canvasElement.clientWidth + "px";
                this._elm.style.height = canvasElement.clientHeight + "px";
                this._elm.style.overflow = "hidden";
                this._elm.style.position = "absolute";

                var bg = detectBackgroundColor(this._elm);
                this.xClearColor = bg;
                this.xClipStyle = bg;

                this._clipRect = this._rect(0, 0, this.canvas.width, this.canvas.height);
            }

            VML2D.prototype = {
                // state
                save: function () {
                    var prop = {}, i;
                    for (i in SaveProps) { prop[i] = this[i]; }
                    this._stack.push([prop, uu.mix([], this._mtx),
                                 this._clipPath ? String(this._clipPath) : null]);
                },
                restore: function () {
                    if (!this._stack.length) { return; }

                    var last = this._stack.pop(), i;
                    for (i in SaveProps) { this[i] = last[0][i]; }
                    this._mtx = last[1];
                    this._clipPath = last[2];
                },
                // transformations
                scale: function (x, y) {
                    ++this._mtxEffected;
                    this._mtx = _matrix.multiply(_matrix.scale(x, y), this._mtx);
                    this._scaleX *= x;
                    this._scaleY *= y;
                    this._lineScale = (this._mtx[0][0] + this._mtx[1][1]) / 2;
                },
                rotate: function (angle) {
                    ++this._mtxEffected;
                    this._mtx = _matrix.multiply(_matrix.rotate(angle), this._mtx);
                },
                translate: function (x, y) {
                    ++this._mtxEffected;
                    this._mtx = _matrix.multiply(_matrix.translate(x, y), this._mtx);
                },
                transform: function (m11, m12, m21, m22, dx, dy) {
                    this._mtx = _matrix.multiply(_matrix.transform(m11, m12, m21, m22, dx, dy), this._mtx);
                },
                setTransform:
              function (m11, m12, m21, m22, dx, dy) {
                  this._mtx = _matrix.transform(m11, m12, m21, m22, dx, dy);
              },
                // rects
                clearRect: function (x, y, w, h) {
                    if (!x && !y &&
                    w == parseInt(this.canvas.width) &&
                    h == parseInt(this.canvas.height)) {
                        // clear all
                        this._elm.innerHTML = "";
                        this._zindex = 0;
                    } else {
                        var _r = _round,
                      c0 = this._map(x, y),
                      c1 = this._map(x + w, y),
                      c2 = this._map(x + w, y + h),
                      c3 = this._map(x, y + h),
                      path = [" m", _r(c0.x), " ", _r(c0.y),
                                  " l", _r(c1.x), " ", _r(c1.y),
                                  " l", _r(c2.x), " ", _r(c2.y),
                                  " l", _r(c3.x), " ", _r(c3.y),
                                  " x"].join(""),
                      zindex = 0,
                      c = _colorAlpha(this.xClearColor),
                      color = c[0],
                      alpha = c[1] * this.globalAlpha,
                      vml;

                        switch (Composite[this.globalCompositeOperation] || 1) {
                            // 5:destination-over     
                            case 5: zindex = --this._zindex; break;
                            // 11:copy     
                            case 11: this._clear(); break;
                        }

                        vml = ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="', path,
                          '"><v:fill class=uvml type="solid" color="', color, '" opacity="', alpha,
                          '" /></v:shape>'].join("");
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                },
                _clear: function () {
                    // clear all
                    this._elm.innerHTML = "";
                    this._zindex = 0;
                },
                fillRect: function (x, y, w, h) {
                    var rect = this._rect(x, y, w, h);
                    this._path = [rect];
                    this._px = x;
                    this._py = y;

                    // When all canvases are painted out, the fillStyle(background-color) is preserved.
                    if (rect === this._clipRect) {
                        if (typeof this.fillStyle === "string") {
                            this.xClipStyle = this.fillStyle;
                        }
                    }
                    this.fill();
                },
                strokeRect: function (x, y, w, h) {
                    this._path = [];
                    this.rect(x, y, w, h);
                    this.stroke();
                },
                // path API
                beginPath: function () {
                    this._path = [];
                },
                closePath: function () {
                    this._path.push(" x");
                },
                moveTo: function (x, y) {
                    var _r = _round,
                    c0 = this._map(x, y);
                    this._path.push("m ", _r(c0.x), " ", _r(c0.y));
                    this._px = x;
                    this._py = y;
                },
                lineTo: function (x, y) {
                    var _r = _round,
                    c0 = this._map(x, y);
                    this._path.push("l ", _r(c0.x), " ", _r(c0.y));
                    this._px = x;
                    this._py = y;
                },
                quadraticCurveTo:
              function (cpx, cpy, x, y) {
                  var _r = _round,
                    cp1x = this._px + 2.0 / 3.0 * (cpx - this._px),
                    cp1y = this._py + 2.0 / 3.0 * (cpy - this._py),
                    cp2x = cp1x + (x - this._px) / 3.0,
                    cp2y = cp1y + (y - this._py) / 3.0,
                    c0 = this._map(x, y),
                    c1 = this._map(cp1x, cp1y),
                    c2 = this._map(cp2x, cp2y);
                  this._path.push("c ", _r(c1.x), " ", _r(c1.y), " ",
                                      _r(c2.x), " ", _r(c2.y), " ",
                                      _r(c0.x), " ", _r(c0.y));
                  this._px = x;
                  this._py = y;
              },
                bezierCurveTo:
              function (cp1x, cp1y, cp2x, cp2y, x, y) {
                  var _r = _round,
                    c0 = this._map(x, y),
                    c1 = this._map(cp1x, cp1y),
                    c2 = this._map(cp2x, cp2y);
                  this._path.push("c ", _r(c1.x), " ", _r(c1.y), " ",
                                      _r(c2.x), " ", _r(c2.y), " ",
                                      _r(c0.x), " ", _r(c0.y));
                  this._px = x;
                  this._py = y;
              },
                // not impl
                arcTo: function (x1, y1, x2, y2, radius) { },
                rect: function (x, y, w, h) {
                    this._path.push(this._rect(x, y, w, h));
                    this._px = x;
                    this._py = y;
                },
                _rect: function (x, y, w, h) {
                    var _r = _round,
                    c0 = this._map(x, y),
                    c1 = this._map(x + w, y),
                    c2 = this._map(x + w, y + h),
                    c3 = this._map(x, y + h);
                    return [" m", _r(c0.x), " ", _r(c0.y),
                        " l", _r(c1.x), " ", _r(c1.y),
                        " l", _r(c2.x), " ", _r(c2.y),
                        " l", _r(c3.x), " ", _r(c3.y),
                        " x"].join("");
                },
                arc: function (x, y, radius, startAngle, endAngle, anticlockwise) {
                    radius *= _zoom;
                    var _r = _round,
                    x1 = x + (_cos(startAngle) * radius) - _halfZoom,
                    y1 = y + (_sin(startAngle) * radius) - _halfZoom,
                    x2 = x + (_cos(endAngle) * radius) - _halfZoom,
                    y2 = y + (_sin(endAngle) * radius) - _halfZoom,
                    c0, c1, c2, rx, ry;

                    if (x1 === x2 && !anticlockwise) { x1 += 0.125; }
                    c0 = this._map(x, y),
                c1 = this._map(x1, y1),
                c2 = this._map(x2, y2),
                rx = this._scaleX * radius,
                ry = this._scaleY * radius;
                    this._path.push(anticlockwise ? "at " : "wa ",
                                _r(c0.x - rx), " ", _r(c0.y - ry), " ",
                                _r(c0.x + rx), " ", _r(c0.y + ry), " ",
                                _r(c1.x), " ", _r(c1.y), " ",
                                _r(c2.x), " ", _r(c2.y));
                },
                fill: function () {
                    var vml = "", path = this._path.join("");
                    if (typeof this.fillStyle === "string") {
                        vml = this._fill(this.fillStyle, path);
                    } else {
                        switch (this.fillStyle._type) {
                            case 1: vml = this._linearGradientFill(this.fillStyle, path); break;
                            case 2: vml = this._radialGradientFill(this.fillStyle, path); break;
                            case 3: vml = this._patternFill(this.fillStyle, path); break;
                        }
                    }
                    if (vml) {
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                    this._path = [];
                },
                _fill: function (style, path, forceOpacity) {
                    var c = _colorAlpha(style),
                    color = c[0],
                    alpha = c[1] * this.globalAlpha,
                    zindex = 0;

                    switch (Composite[this.globalCompositeOperation] || 1) {
                        // 5:destination-over     
                        case 5: zindex = --this._zindex; break;
                        // 11:copy     
                        case 11: this._clear(); break;
                    }

                    return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="',
                          path,
                        '"><v:fill class=uvml type="solid" color="', color, '" opacity="', forceOpacity || alpha, '" />',
                        '</v:shape>'].join("");
                },
                _linearGradientFill:
              function (style, path, forceOpacity) {
                  var fp = style._param,
                    color = this._buildColor(style._colorStop),
                    alpha = this.globalAlpha,
                    zindex = 0,
                    angle = Math.atan2(Math.pow(fp.x1 - fp.x0, 2),
                                         Math.pow(fp.y1 - fp.y0, 2)) * Math.toDegrees;
                  if (fp.x0 > fp.x1) { angle += 90; }
                  if (fp.y0 > fp.y1) { angle += 90; }
                  if (angle >= 360) { angle -= 360; }

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }

                  return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="', path,
                        '"><v:fill class=uvml type="gradient" method="sigma" focus="0%" colors="', color, '" opacity="', forceOpacity || alpha, '" angle="', angle,
                        '" /></v:shape>'].join("");
              },
                _radialGradientFill:
              function (style, path, forceOpacity) {
                  var rv = [], fp = style._param, fsize, fposX, fposY, v,
                    color = this._buildColor(style._colorStop),
                    alpha = this.globalAlpha,
                    zindex = 0,
                    zindex2 = 0,
                    x = fp.x1 - fp.r1,
                    y = fp.y1 - fp.r1,
                    r1x = fp.r1 * this._scaleX,
                    r1y = fp.r1 * this._scaleY,
                    c0 = this._map(x, y);

                  // focus
                  fsize = (fp.r0 / fp.r1);
                  // forcus position x
                  fposX = (1 - fsize + (fp.x0 - fp.x1) / fp.r1) / 2;
                  // forcus position y
                  fposY = (1 - fsize + (fp.y0 - fp.y1) / fp.r1) / 2;

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; zindex2 = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }

                  if (style._colorStop.length) {
                      v = style._colorStop[0];
                      if (v.color[1] > 0.001) {
                          // fill outside
                          rv.push('<v:shape class=uvml style="z-index:', zindex2, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="', path,
                            '"><v:fill class=uvml type="solid" color="', v.color[0], '" opacity="', forceOpacity || v.color[1] * alpha,
                            '" /></v:shape>');
                      }
                  }
                  rv.push('<v:oval class=uvml style="z-index:', zindex, ';position:absolute;left:', _round(c0.x / _zoom), 'px;top:', _round(c0.y / _zoom), 'px;width:', r1x, 'px;height:', r1y,
                          'px" stroked="f" coordorigin="0,0" coordsize="11000,11000"><v:fill class=uvml type="gradientradial" method="sigma" focussize="', fsize,
                          ',', fsize, '" focusposition="', fposX, ',', fposY, '" opacity="', forceOpacity || alpha, '" colors="', color,
                        '" /></v:oval>');
                  return rv.join("");
              },
                _patternFill:
              function (style, path, forceOpacity) {
                  var src = style._src,
                    alpha = this.globalAlpha,
                    zindex = 0;

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }
                  return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="', path,
                        '"><v:fill class=uvml type="tile" opacity="', forceOpacity || alpha, '" src="', src,
                        '" /></v:shape>'].join("");
              },
                stroke: function () {
                    var vml = "";
                    if (typeof this.strokeStyle === "string") {
                        vml = this._stroke(this.strokeStyle);
                    } else {
                        switch (this.strokeStyle._type) {
                            case 1: vml = this._linearGradientStroke(this.strokeStyle); break;
                            case 2: vml = this._radialGradientStroke(this.strokeStyle); break;
                            case 3: vml = this._patternStroke(this.strokeStyle); break;
                        }
                    }
                    if (vml) {
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                    this._path = [];
                },
                _stroke: function (style) {
                    var c = _colorAlpha(style),
                    color = c[0],
                    alpha = c[1] * this.globalAlpha,
                    path = this._path.join(""),
                    zindex = 0,
                    weight = this.lineWidth * this._lineScale,
                    join = this.lineJoin,
                    miter = this.miterLimit,
                    cap = VMLCaps[this.lineCap] || "square";

                    switch (Composite[this.globalCompositeOperation] || 1) {
                        // 5:destination-over     
                        case 5: zindex = --this._zindex; break;
                        // 11:copy     
                        case 11: this._clear(); break;
                    }

                    return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="f" stroked="t" coordorigin="0,0" coordsize="100,100" path="', path,
                        '"><v:stroke class=uvml color="', color, '" weight="', weight, 'px" endcap="', cap, '" opacity="', alpha, '" joinstyle="', join, '" miterlimit="', miter,
                        '" /></v:shape>'].join("");
                },
                _linearGradientStroke:
              function (style) {
                  var fp = style._param,
                    path = this._path.join(""),
                    zindex = 0,
                    color = this._buildColor(style._colorStop),
                    alpha = this.globalAlpha,
                    weight = this.lineWidth * this._lineScale,
                    join = this.lineJoin,
                    miter = this.miterLimit,
                    cap = VMLCaps[this.lineCap] || "square",
                    angle = Math.atan2(Math.pow(fp.x1 - fp.x0, 2),
                                         Math.pow(fp.y1 - fp.y0, 2)) * Math.toDegrees;
                  if (fp.x0 > fp.x1) { angle += 90; }
                  if (fp.y0 > fp.y1) { angle += 90; }
                  if (angle >= 360) { angle -= 360; }

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }

                  return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="f" stroked="t" coordorigin="0,0" coordsize="100,100" path="', path,
                        '"><v:stroke class=uvml filltype="solid" colors="', color, '" angle="', angle,
                        '" weight="', weight, 'px" endcap="', cap, '" opacity="', alpha, '" joinstyle="', join, '" miterlimit="', miter,
                        '" /></v:shape>'].join("");
              },
                _radialGradientStroke:
              function (style) {
                  var rv = [], fp = style._param, v,
                    path,
                    zindex = 0,
                    zindex2 = 0,
                    weight = this.lineWidth * this._lineScale,
                    join = this.lineJoin,
                    miter = this.miterLimit,
                    cap = VMLCaps[this.lineCap] || "square",
                    x = fp.x1 - fp.r1,
                    y = fp.y1 - fp.r1,
                    r1x = fp.r1 * this._scaleX,
                    r1y = fp.r1 * this._scaleY,
                    c0 = this._map(x, y);

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; zindex2 = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }

                  if (style._colorStop.length) {
                      v = style._colorStop[0];
                      if (v.color[1] > 0.001) {
                          // fill outside
                          path = this._path.join("");
                          rv.push('<v:shape class=uvml style="z-index:', zindex2, ';position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="', path,
                            '"><v:fill class=uvml type="solid" color="', v.color[0], '" opacity="', v.color[1] * this.globalAlpha,
                            '" /></v:shape>');
                      }
                      v = style._colorStop[_round(style._colorStop.length / 2) - 1];
                      rv.push('<v:oval class=uvml style="z-index:', zindex, ';position:absolute;left:', _round(c0.x / _zoom), 'px;top:', _round(c0.y / _zoom), 'px;width:', r1x, 'px;height:', r1y,
                            'px" stroked="t" coordorigin="0,0" coordsize="11000,11000">',
                            '<v:stroke class=uvml filltype="tile" weight="', weight, 'px" endcap="', cap, '" joinstyle="', join, '" miterlimit="', miter,
                            ',', '" opacity="', v.color[1] * this.globalAlpha, '" color="', v.color[0],
                          '" /></v:oval>');
                  }
                  return rv.join("");
              },
                _patternStroke:
              function (style) {
                  var path = this._path.join(""),
                    zindex = 0,
                    src = style._src,
                    alpha = this.globalAlpha,
                    weight = this.lineWidth * this._lineScale,
                    join = this.lineJoin,
                    miter = this.miterLimit,
                    cap = VMLCaps[this.lineCap] || "square";

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      // 5:destination-over     
                      case 5: zindex = --this._zindex; break;
                      // 11:copy     
                      case 11: this._clear(); break;
                  }
                  return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:10px;height:10px" filled="f" stroked="t" coordorigin="0,0" coordsize="100,100" path="', path,
                        '"><v:stroke class=uvml filltype="tile" weight="', weight, 'px" endcap="', cap, '" opacity="', alpha, '" joinstyle="', join, '" miterlimit="', miter,
                        '" src="', src,
                        '" /></v:shape>'].join("");
              },
                clip: function () {
                    this._clipPath = this._clipRect + " x " + this._path.join("");
                },
                _clippy: function (vml) {
                    return [vml, '<v:shape class=uvml style="position:absolute;width:10px;height:10px" filled="t" stroked="f" coordorigin="0,0" coordsize="100,100" path="',
                          this._clipPath,
                        '"><v:fill class=uvml type="solid" color="', this.xClipStyle, '" />',
                        '</v:shape>'].join("");
                },
                isPointInPath:
                // not impl
              function (x, y) { },
                // text
                fillText: function (text, x, y, maxWidth) {
                    var vml;
                    if (typeof this.fillStyle === "string") {
                        vml = this._fillText(text, x, y, maxWidth, this.fillStyle);
                    } else {
                        switch (this.fillStyle._type) {
                            case 1: vml = this._linearGradientFillText(text, x, y, maxWidth, this.fillStyle); break;
                            case 2: vml = this._radialGradientFillText(text, x, y, maxWidth, this.fillStyle); break;
                            case 3: vml = this._patternFillText(text, x, y, maxWidth, this.fillStyle); break;
                        }
                    }
                    if (vml) {
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                },
                _fillText: function (text, x, y, maxWidth, style) {
                    text = text.replace(/( |\t|\v|\f|\r\n|\r|\n)/g, " ");
                    var c = _colorAlpha(style),
                    color = c[0],
                    alpha = c[1] * this.globalAlpha,
                    zindex = 0,
                    align = TextAlign[this.textAlign] || 1,
                    dim = textRect(text, this.font),
                    w, h, offX = 0, tune;

                    tune = fontScalFineTuning(parseFontCSS(this.font).FontSize, 1);

                    w = dim.w * this.xFontScaleW;
                    h = dim.h * this.xFontScaleH * tune.scale;
                    if (align === 2) {
                        offX = w / 2;
                    } else if (align === 3) {
                        offX = w;
                    }

                    switch (Composite[this.globalCompositeOperation] || 1) {
                        case 5: zindex = --this._zindex; break;
                        case 11: this._clear(); break;
                    }

                    return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:', w, 'px;height:', h,
                          'px;left:', x - offX, 'px;top:', y + this.xTextMarginTop + tune.margin,
                          'px;" stroked="f" coordSize="21600,21600" o:spt="136" strokeColor="', color, '" fillColor="', color,
                          ' opacity="', alpha, '">',
                        '<v:textpath class=uvml style="font:', this.font, '" string="', text, '" />',
                        '</v:shape>'].join("");
                },
                strokeText: function (text, x, y, maxWidth) {
                    var vml;
                    if (typeof this.fillStyle === "string") {
                        vml = this._strokeText(text, x, y, maxWidth, this.strokeStyle);
                    } else {
                    }
                    if (vml) {
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                },
                _strokeText:
              function (text, x, y, maxWidth, style) {
                  text = text.replace(/( |\t|\v|\f|\r\n|\r|\n)/g, " ");
                  var c = _colorAlpha(style),
                    color = c[0],
                    alpha = c[1] * this.globalAlpha,
                    zindex = 0,
                    dim = textRect(text, this.font),
                    align = TextAlign[this.textAlign] || 1,
                    w, h, offX = 0, tune;

                  tune = fontScalFineTuning(parseFontCSS(this.font).FontSize, 1);

                  w = dim.w * this.xFontScaleW;
                  h = dim.h * this.xFontScaleH * tune.scale;
                  if (align === 2) {
                      offX = w / 2;
                  } else if (align === 3) {
                      offX = w;
                  }

                  switch (Composite[this.globalCompositeOperation] || 1) {
                      case 5: zindex = --this._zindex; break;
                      case 11: this._clear(); break;
                  }

                  return ['<v:shape class=uvml style="z-index:', zindex, ';position:absolute;width:', w, 'px;height:', h,
                          'px;left:', x - offX, 'px;top:', y + this.xTextMarginTop + tune.margin,
                          'px;" stroked="t" filled="f" coordSize="21600,21600" o:spt="136" strokeColor="', color, '" fillColor="', color,
                          ' opacity="', alpha, '">',
                        '<v:textpath class=uvml style="font:', this.font, '" string="', text, '" />',
                        '</v:shape>'].join("");
              },
                measureText:
              function (text) {
                  var dim = textRect(text, this.font),
                    w = dim.w * this.xFontScaleW,
                    h = dim.h * this.xFontScaleH;
                  return new VMLTextMetrics(w, h);
              },

                // drawing images
                // drawImage(image, dx, dy)
                // drawImage(image, dx, dy, dw, dh)
                // drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
                drawImage: function (image) {
                    var vml = "", a = arguments,
                    dx, dy, dw, dh, sx, sy, sw, sh, iw, ih, method;

                    if ("src" in image) {
                        // image is HTMLImageElement
                        iw = image.width;
                        ih = image.height;
                        switch (a.length) {
                            case 3: dx = a[1], dy = a[2], dw = iw, dh = ih,
                          sx = 0, sy = 0, sw = iw, sh = ih, method = "image"; break;
                            case 5: dx = a[1], dy = a[2], dw = a[3], dh = a[4],
                          sx = 0, sy = 0, sw = iw, sh = ih, method = "scale"; break;
                            case 9: dx = a[5], dy = a[6], dw = a[7], dh = a[8],
                          sx = a[1], sy = a[2], sw = a[3], sh = a[4], method = "scale"; break;
                            default: throw TypeError("CanvasRenderingContext2D.drawImage");
                        }
                        vml = this._drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh, iw, ih, method);
                        this._elm.insertAdjacentHTML("beforeEnd", this._clipPath ? this._clippy(vml) : vml);
                    }
                },
                _drawImage: function (image, sx, sy, sw, sh, dx, dy, dw, dh, ew, eh, method) {
                    var me = this, rv = [], _r = _round, c0 = this._map(dx, dy),
                    zindex = 0,
                    // filter prefix
                    prefix = "filter:progid:DxImageTransform.Microsoft",
                    // 0: none size transform, 1: size transform
                    sizeTrans = (sx || sy);

                    function trans(x, y, w, h) {
                        var m = me._mtx,
                      _r = _round,
                      c1 = me._map(x, y),
                      c2 = me._map(x + w, y),
                      c3 = me._map(x, y + h),
                      c4 = me._map(x + w, y + h),
                      mx = ["M11='", m[0][0], "',M12='", m[1][0],
                            "',M21='", m[0][1], "',M22='", m[1][1],
                            "',Dx='", _r(c1.x / _zoom), "',Dy='", _r(c1.y / _zoom), "'"],
                      rv = ["padding:0 ", _r(Math.max(c1.x, c2.x, c3.x, c4.x) / _zoom), "px ",
                                          _r(Math.max(c1.y, c2.y, c3.y, c4.y) / _zoom), "px 0;",
                            "filter:progid:DXImageTransform.Microsoft.Matrix(", mx.join(""), ",sizingmethod='clip')"];
                        return rv.join("");
                    }

                    switch (Composite[this.globalCompositeOperation] || 1) {
                        case 5: zindex = --this._zindex; break;
                        case 11: this._clear(); break;
                    }

                    if (this._mtxEffected) {
                        rv.push('<div style="z-index:', zindex, ';position:absolute;', trans(dx, dy, dw, dh), '">');
                    } else {
                        rv.push('<div style="z-index:', zindex, ';position:absolute;',
                                      "top:", _r(c0.y / _zoom), "px;left:", _r(c0.x / _zoom), "px", '">');
                    }
                    rv.push('<div style="position:relative;overflow:hidden;width:',
                                     _r(dw), 'px;height:', _r(dh), 'px">');

                    if (sizeTrans) {
                        rv.push('<div style="width:', _cos(dw + sx * dw / sw), 'px;',
                                      'height:', _cos(dh + sy * dh / sh), 'px;',
                                      prefix, '.Matrix(Dx=', -sx * dw / sw, ',Dy=', -sy * dh / sh, ')">');
                    }
                    rv.push('<div style="width:', _r(ew * dw / sw), 'px;',
                                    'height:', _r(eh * dh / sh), 'px;',
                                    prefix, '.AlphaImageLoader(src=', image.src, ',sizingMethod=' + method + ')"></div>');
                    rv.push(sizeTrans ? "</div>" : "", "</div></div>");
                    return rv.join("");
                },
                // pixel manipulation
                createImageData:
                // not impl
              function (sw, sh) { },
                getImageData:
                // not impl
              function (sx, sy, sw, sh) { },
                putImageData:
                // not impl
              function (imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) { },
                // gradient
                createLinearGradient:
              function (x0, y0, x1, y1) {
                  return new VMLGrad(1,
                  // 1:gradient
                                   {x0: x0, y0: y0, x1: x1, y1: y1 });
              },
                createRadialGradient:
              function (x0, y0, r0, x1, y1, r1) {
                  return new VMLGrad(2,
                  // 2:gradientradial
                                   {x0: x0, y0: y0, r0: r0,
                                   x1: x1, y1: y1, r1: r1
                               });
              },
                createPattern:
              function (image, repetition) {
                  return new VMLPatt(image, repetition);
              },
                _buildColor:
              function (ary) {
                  var rv = [], i = 0, iz = ary.length;
                  for (; i < iz; ++i) {
                      rv.push(ary[i].offset + " " + ary[i].color[0]);
                  }
                  return rv.join(",");
              },
                _map: function (x, y) {
                    return {
                        // x: x * m11 + y * m21 + offsetX
                        // y: x * m12 + y * m22 + offsetY
                        x: _zoom * (x * this._mtx[0][0] + y * this._mtx[1][0] + this._mtx[2][0]) - _halfZoom,
                        y: _zoom * (x * this._mtx[0][1] + y * this._mtx[1][1] + this._mtx[2][1]) - _halfZoom
                    };
                },
                xClearBegin:
                // clearRect(all) + beginPath + moveTo
              function (x, y) {
                  if (this._node) {
                      // inline clearRect(all)
                      // clear all
                      this._elm.innerHTML = "";

                      this._zindex = 0;
                      // inline beginPath + moveTo
                      var _r = _round, c0 = this._map(x, y);
                      this._path = ["m ", _r(c0.x), " ", _r(c0.y)];
                      this._px = x;
                      this._py = y;
                  }
              }
            };

            function VMLGrad(type, param) {
                this._type = type;
                this._param = param;
                this._colorStop = [];
            }

            VMLGrad.prototype.addColorStop = function (offset, color) {
                function fn(a, b) { return a.offset - b.offset; }
                // collision of the offset is evaded
                var v, i = 0, iz = this._colorStop.length;
                for (; i < iz; ++i) {
                    v = this._colorStop[i];
                    if (v.offset === offset) {
                        if (offset < 1 && offset > 0) {
                            offset += iz / 1000;
                        }
                    }
                }
                this._colorStop.push({ offset: 1 - offset, color: _colorAlpha(color) });
                this._colorStop.sort(fn);
            };

            function VMLPatt(image, repetition) {
                repetition = repetition || "repeat";
                switch (repetition) {
                    case "repeat": break;
                    default: throw TypeError("bad argument");
                }

                this._type = 3; // 3:tile

                if ("src" in image) {
                    // HTMLImageElement
                    if ("uuIEBoostAlphapngSrc" in image) {
                        // special attr by uupaa.js
                        this._src = image.uuIEBoostAlphapngSrc;
                    } else {
                        this._src = image.src;
                    }
                    this._repeat = repetition;
                } else if ("getContext" in image) {
                    // HTMLCanvasElement
                    throw Error("not impl");
                }
            }

            function VMLTextMetrics(w, h) {
                this.width = w;
                this.height = h;
            }

            function detectBackendLibrary() {
                function parseQuery(qstr) {
                    var rv = {}, F = _win.decodeURIComponent;
                    function D(_, key, value) { return rv[F(key.toLowerCase())] = F(value.toLowerCase()); }
                    qstr.replace(/^.*\?/, "").replace(/&amp;/g, "&").replace(/(?:([^\=]+)\=([^\&]+)&?)/g, D);
                    return rv;
                }
                var e = _doc.getElementById("uupaa-excanvas.js");
                if (e && e.src.indexOf("?") !== -1) {
                    uu.mix(_env, parseQuery(e.src.slice(e.src.indexOf("?") + 1)));
                }
            }

            function initCanvas() {
				 var userAgent = navigator.userAgent.toLowerCase();
                if (!_doc.uniqueID || /trident/.test(userAgent) || !document.all) {
                    if (_win.opera) {
                        _win.addEventListener("load", function () { ++_ready; }, false);
                    } else if (/WebKit/.test(navigator.userAgent) && _doc.readyState) {
                        (function () {
                            if (/loaded|complete/.test(_doc.readyState)) { ++_ready; return; }
                            setTimeout(arguments.callee, 16);
                        })();
                    } else if (/Gecko\//.test(navigator.userAgent)) {
                        _doc.addEventListener("DOMContentLoaded", function () { ++_ready; }, false);
                    } else {
                        ++_ready;
                    }
                } else {
                    if (/msie/.test(userAgent) && !/opera/.test(userAgent)) {

                        var version = parseFloat(userAgent.match(/msie (\d+)/)[1]);
                        var quirks = (document.compatMode == 'BackCompat');
                        var ie8 = !!document.documentMode && document.documentMode < 9;
                        /**
                        ie8标准模式
                        */
                        var ie8Compat = document.documentMode == 8;
                        /**
                        ie9标准模式
                        */
                        //   var ie9Compat = document.documentMode == 9;

                        /**
                        ie7模式
                        */
                        var ie7Compat = ((version == 7 && !document.documentMode)
					|| document.documentMode == 7);
                        /**
                        ie6模式
                        */
                        var ie6Compat = (version < 7 || quirks);

                        if (ie8Compat || ie7Compat || ie6Compat || ie8) {
                            //ie页面刷新后，访问namespaces，会引发未指明错误，解决方法是这段代码要在window.onload执行；或者放到settimeout中可以减少错误发生；
                            if (!_doc.namespaces["v"]) {
                                _doc.namespaces.add("v", "urn:schemas-microsoft-com:vml");
                            }
                            if (!_doc.namespaces["o"]) {
                                _doc.namespaces.add("o", "urn:schemas-microsoft-com:office:office");
                            }
                            _doc.createStyleSheet().cssText =
      "canvas{display:inline-block;text-align:left;width:300px;height:150px}" + "v\\:*{behavior:url(#default#VML)} .uvml{behavior: url(#default#VML);}" +
      "o\\:*{behavior:url(#default#VML)}";
                        }

                    }

                    _win.CanvasError = function (sender, arg) {
                        var e = _doc.getElementById("output");
                        e && (e.innerHTML = ["type=", arg.ErrorType,
                           "code=", arg.ErrorCode,
                           "msg=", arg.ErrorMessage].join(" "));
                    };

                    _doc.attachEvent("onreadystatechange", function () {
                        if (!/loaded|complete/.test(_doc.readyState)) { return; }

                        var e = _doc.getElementsByTagName("canvas"), v, i = 0, ez = e.length, vml = 0, ag = 0;

                        if (ez) {
                            for (; i < ez; ++i) {
                                v = e[i];
                                if (!v.getContext) {

                                    if ((" " + v.className + " ").indexOf(" vml ") > -1) {
                                        VMLInitDynamicElement(v);
                                        ++vml;
                                    } else {
                                    }
                                }
                            }
                        }
                        if (!ag) {
                            ++_ready;
                        }
                    });
                }
            }

            // --- export ---
            uuClass.Canvas.VML2D = VML2D;
            uuClass.Canvas.VMLGradient = VMLGrad;
            uuClass.Canvas.VMLPattern = VMLPatt;
            uuClass.Canvas.VMLTextMetrics = VMLTextMetrics;
            if (_doc.uniqueID) {
                _win.CanvasRenderingContext2D = function () { };
                _win.CanvasGradient = function () { };
                _win.CanvasPattern = function () { };
            }

            uuClass.Canvas.InitCavansElement = function (elem) {
                if (elem == null)
                    return;
                if (!elem.getContext) {

                    if ((" " + elem.className + " ").indexOf(" vml ") > -1) {
                        VMLInitDynamicElement(elem);
                    } else {
                    }
                }
            };

            // --- initialize ---
            detectBackendLibrary();
            initCanvas();

        })();

    }
});
